/**
 * @author WY
 * @since 2023/11
 */

import {
  anyFunc,
  Button,
  CENTEREVENTS,
  coreObserver,
  EventCenterRawEvents,
  EventsRes,
  MouseInfo,
  pickingData,
  SceneSetup,
  STATE,
} from '@amcax/base';
import { preventKeys } from '@app-cad/common/Constants';
import { ctx2 } from '@app-cad/module';

interface EventTypeMap {
  [CENTEREVENTS.POINTERMOVE]: PointerEvent;
  [CENTEREVENTS.POINTERDOWN]: PointerEvent;
  [CENTEREVENTS.POINTERUP]: PointerEvent;
  [CENTEREVENTS.CLICK]: PointerEvent;
  [CENTEREVENTS.DBCLICK]: PointerEvent;
  [CENTEREVENTS.KEYUP]: KeyboardEvent;
  [CENTEREVENTS.KEYDOWN]: KeyboardEvent;
  [CENTEREVENTS.KEYPRESS]: KeyboardEvent;
}

export const getPointerButton = (event: EventCenterRawEvents): Button => {
  if ((event as PointerEvent).button == null) {
    return null;
  }

  const pointerEvent = event as PointerEvent;

  if (pointerEvent.button === -1) {
    // pointermove状态 https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent/buttons
    // 仅考虑鼠标按键，不考虑浏览器的前进后退（8，16）
    // 将buttons转化为二进制，从个位往左，每一项为1表示已按下，不然则为未按下

    const buttonsInfo = pointerEvent.buttons
      .toString(2)
      .padStart(3, '0')
      .slice(-3);

    const onLeft = buttonsInfo[2] === '1';
    const onMiddle = buttonsInfo[0] === '1';
    const onRight = buttonsInfo[1] === '1';

    return {
      onLeft,
      onMiddle,
      onRight,
      buttonDown: onLeft || onRight || onMiddle,
    };
  } else {
    // pointerdown / pointerup状态
    return {
      onLeft: pointerEvent.button == 0,
      onMiddle: pointerEvent.button == 1,
      onRight: pointerEvent.button == 2,
      buttonDown:
        pointerEvent.button == 0 ||
        pointerEvent.button == 1 ||
        pointerEvent.button == 2,
    };
  }
};

/* 事件模块 */
export abstract class BaseEventCenter extends coreObserver {
  id: string;
  state: string; // 事件状态
  sceneSetup: SceneSetup;
  canvas: HTMLElement;
  pickingData: pickingData;
  mouse: MouseInfo; // 鼠标信息
  pickedHoleId: string; // 定位中的洞口id
  private _events: Record<string, anyFunc>; // 事件集合
  private _documentEvents: Record<string, anyFunc>; // 事件集合
  static pickVertex: any;

  constructor() {
    super();
    this.sceneSetup = ctx2.viewer.sceneSetup;
    this.canvas = this.sceneSetup.container;
    this.pickingData = this.sceneSetup.pickingData;
    this.state = STATE.SLEEPING;
    this.mouse = {
      x: -10000,
      y: -10000,
    };
    // this.init();
  }

  delete(): void {
    return null;
  }

  init() {
    if (this.state != STATE.SLEEPING) {
      return;
    }
    this.bindEvent(CENTEREVENTS.POINTERDOWN, this.canvasPointerdown.bind(this));
    this.bindEvent(CENTEREVENTS.POINTERUP, this.canvasPointerup.bind(this));
    this.bindEvent(CENTEREVENTS.POINTERMOVE, this.canvasPointermove.bind(this));
    this.bindEvent(CENTEREVENTS.DBCLICK, this.canvasDblClick.bind(this));
    this.bindEvent(CENTEREVENTS.CLICK, this.canvasClick.bind(this));
    // offCanvasEvent
    this.bindDocumentEvent(
      CENTEREVENTS.POINTERUP,
      this.offCanvasPointerUp.bind(this),
    );
    this.state = STATE.DEFAULT;
    /** key event */
    this.bindDocumentEvent(CENTEREVENTS.KEYUP, this.keyUp.bind(this));
    this.bindDocumentEvent(CENTEREVENTS.KEYDOWN, this.keyDown.bind(this));
    this.bindDocumentEvent(CENTEREVENTS.KEYPRESS, this.keyPress.bind(this));
  }

  // 暂时关闭事件中心
  sleep() {
    this.offEvent(CENTEREVENTS.POINTERDOWN);
    this.offEvent(CENTEREVENTS.POINTERUP);
    this.offEvent(CENTEREVENTS.POINTERMOVE);
    this.offEvent(CENTEREVENTS.DBCLICK);
    this.offEvent(CENTEREVENTS.CLICK);
    // offcanvas
    this.offDocumentEvent(CENTEREVENTS.POINTERUP);
    this.offDocumentEvent(CENTEREVENTS.KEYDOWN);
    this.offDocumentEvent(CENTEREVENTS.KEYUP);
    this.offDocumentEvent(CENTEREVENTS.KEYPRESS);
    this.state = STATE.SLEEPING;
    this.listeners.clear();
  }

  // 修改state
  abstract setState(state: string): this;

  // 恢复默认
  abstract setDefault(force?: boolean): this;

  getEventRes<T extends EventCenterRawEvents>(
    event: T,
    eventName: string,
  ): EventsRes<T> {
    const button = getPointerButton(event);

    return {
      eventName,
      button,
      mouse: this.mouse,
      event,
    };
  }

  onCanvasDbClick?(ev: EventsRes<PointerEvent>): void;
  // 鼠标双击
  canvasDblClick(event: PointerEvent) {
    // 建模模式
    if (this.state === STATE.DEFAULT) {
      // 判断是否可以进入组编辑
      // if (!ctx?.interactionData?.cmdParams?.groupingActive) {
      //   initEditGroup()
      // }
    }
    const ev = this.getEventRes(event, CENTEREVENTS.DBCLICK);
    this.onCanvasDbClick?.(ev);
    this.emit(CENTEREVENTS.DBCLICK, ev);
  }

  onCanvasClick?(ev: EventsRes<PointerEvent>): void;
  // 鼠标单击
  canvasClick(event: PointerEvent) {
    (document.activeElement as any)?.blur?.();

    const ev = this.getEventRes(event, CENTEREVENTS.CLICK);
    this.onCanvasClick?.(ev);
    this.emit(CENTEREVENTS.CLICK, ev);
  }

  onCanvasPointerDown?(ev: EventsRes<PointerEvent>): void;
  canvasPointerdown(event: PointerEvent) {
    const ev = this.getEventRes(event, CENTEREVENTS.POINTERDOWN);
    this.onCanvasPointerDown?.(ev);
    this.emit(CENTEREVENTS.POINTERDOWN, ev);
  }

  onCanvasPointerup?(ev: EventsRes<PointerEvent>): void;
  canvasPointerup(event: PointerEvent) {
    const ev = this.getEventRes(event, CENTEREVENTS.POINTERUP);

    this.onCanvasPointerup?.(ev);
    this.emit(CENTEREVENTS.POINTERUP, ev);
  }

  /**
   *
   * pointermove 事件
   */
  onCanvasPointerMove?(ev: EventsRes<PointerEvent>): void;
  canvasPointermove(event: PointerEvent) {
    const ev = this.getEventRes(event, CENTEREVENTS.POINTERMOVE);

    const rect = this.canvas.getBoundingClientRect();
    this.mouse = {
      x: Math.round(event.clientX - rect.left),
      y: Math.round(event.clientY - rect.top),
    };

    this.onCanvasPointerMove?.(ev);
    this.emit(CENTEREVENTS.POINTERMOVE, ev);
  }

  //*****pointerMove end */
  /**
   *
   * offCavasEvent
   */
  abstract offCanvasPointerUp(event: PointerEvent);
  /**
   * 键盘事件
   */
  onKeyUp?(ev: EventsRes<KeyboardEvent>): void;
  keyUp(event: KeyboardEvent) {
    // 进入键盘事件
    if (preventKeys.indexOf(event.key) != -1) {
      event.preventDefault();
    }

    const ev: EventsRes<KeyboardEvent> = {
      eventName: CENTEREVENTS.KEYUP,
      event,
      key: event.key,
    };

    this.onKeyUp?.(ev);
    this.emit(CENTEREVENTS.KEYUP, ev);
  }

  onKeyDown?(ev: EventsRes<KeyboardEvent>): void;
  keyDown(event: KeyboardEvent) {
    if (preventKeys.indexOf(event.key) != -1) {
      event.preventDefault();
    }
    // 进入键盘事件
    const ev: EventsRes<KeyboardEvent> = {
      eventName: CENTEREVENTS.KEYDOWN,
      event,
      key: event.key,
    };
    this.onKeyDown?.(ev);
    this.emit(CENTEREVENTS.KEYDOWN, ev);
  }

  onKeyPress?(ev: EventsRes<KeyboardEvent>): void;
  keyPress(event: KeyboardEvent) {
    if (preventKeys.indexOf(event.key) != -1) {
      event.preventDefault();
    }
    // 进入键盘事件
    const ev: EventsRes<KeyboardEvent> = {
      eventName: CENTEREVENTS.KEYPRESS,
      event,
      key: event.key,
    };
    this.onKeyPress?.(ev);
    this.emit(CENTEREVENTS.KEYPRESS, ev);
  }

  //事件监听

  bindDocumentEvent(name: string, callback: (event: any) => void) {
    // 不同设备特殊处理 todo
    const wrappedCallback = (event: any) => {
      callback(event);
    };
    this._documentEvents = this._documentEvents || {};
    this._documentEvents[name] = wrappedCallback;
    document.addEventListener(name, wrappedCallback);
  }

  offDocumentEvent(name: string) {
    // 不同设备特殊处理 todo
    if (this._documentEvents?.[name]) {
      document.removeEventListener(name, this._documentEvents[name]);
      delete this._documentEvents[name];
    }
  }

  bindEvent(name: string, callback: anyFunc) {
    // 不同设备特殊处理 todo
    const wrappedCallback = (event) => {
      callback(event);
    };
    this._events = this._events || {};
    this._events[name] = wrappedCallback;
    this.canvas.addEventListener(name, wrappedCallback);
  }

  offEvent(name: string) {
    // 不同设备特殊处理 todo
    if (this._events?.[name]) {
      this.canvas.removeEventListener(name, this._events[name]);
      delete this._events[name];
    }
  }

  on<K extends string>(
    name: K,
    callback: (
      ev: K extends keyof EventTypeMap ? EventsRes<EventTypeMap[K]> : any,
    ) => any,
  ) {
    return super.on(name, callback);
  }

  off<K extends string>(
    name: K,
    callback: (
      ev: K extends keyof EventTypeMap ? EventsRes<EventTypeMap[K]> : any,
    ) => any,
  ) {
    return super.off(name, callback);
  }
}
